package de.uni_bremen.exceptions.oops;

/**
 * Die Klasse repräsentiert einen Ausdruck mit einem binären Operator im Syntaxbaum.
 */
class BinaryExpression extends Expression implements IEvaluable
{
	/** Der linke Operand. */
	Expression	leftOperand;
	
	/** Der Operator. */
	Symbol.Id	operator;
	
	/** Der rechte Operand. */
	Expression	rightOperand;
	
	/**
	 * Konstruktor.
	 * 
	 * @param operator
	 *            Der Operator.
	 * @param leftOperand
	 *            Der linke Operand.
	 * @param rightOperand
	 *            Der rechte Operand.
	 * @param position
	 *            Die Position, an der dieser Ausdruck im Quelltext beginnt.
	 */
	BinaryExpression(Expression leftOperand, Symbol.Id operator, Expression rightOperand)
	{
		super(leftOperand.position);
		this.leftOperand = leftOperand;
		this.operator = operator;
		this.rightOperand = rightOperand;
	}
	
	/**
	 * Die Methode wertet die BinaryExpression auf konstante Ausdrücke aus.
	 */
	public Expression eval()
	{
		if (leftOperand instanceof IEvaluable)
			leftOperand = ((IEvaluable) leftOperand).eval();
		if (rightOperand instanceof IEvaluable)
			rightOperand = ((IEvaluable) rightOperand).eval();
		if (leftOperand instanceof LiteralExpression && rightOperand instanceof LiteralExpression)
		{
			LiteralExpression leftLiteral = ((LiteralExpression) leftOperand);
			LiteralExpression rightLiteral = ((LiteralExpression) rightOperand);
			if (leftLiteral.type != ClassDeclaration.nullType && rightLiteral.type != ClassDeclaration.nullType)
			{
				int newValue = 0;
				switch (operator)
				{
					case PLUS:
						newValue = leftLiteral.value + rightLiteral.value;
						break;
					case MINUS:
						newValue = leftLiteral.value - rightLiteral.value;
						break;
					case TIMES:
						newValue = leftLiteral.value * rightLiteral.value;
						break;
					case DIV:
						newValue = leftLiteral.value / rightLiteral.value;
						break;
					case MOD:
						newValue = leftLiteral.value % rightLiteral.value;
						break;
					case GT:
						newValue = leftLiteral.value > rightLiteral.value ? 1 : 0;
						break;
					case GTEQ:
						newValue = leftLiteral.value >= rightLiteral.value ? 1 : 0;
						break;
					case LT:
						newValue = leftLiteral.value < rightLiteral.value ? 1 : 0;
						break;
					case LTEQ:
						newValue = leftLiteral.value <= rightLiteral.value ? 1 : 0;
						break;
					case EQ:
						newValue = leftLiteral.value == rightLiteral.value ? 1 : 0;
						break;
					case NEQ:
						newValue = leftLiteral.value != rightLiteral.value ? 1 : 0;
						break;
					case AND:
					case ANDTHEN:
						newValue = leftLiteral.value == 1 && rightLiteral.value == 1 ? 1 : 0;
						break;
					case OR:
					case ORELSE:
						newValue = leftLiteral.value == 1 || rightLiteral.value == 1 ? 1 : 0;
						break;
					default:
						assert false;
				}
				return new LiteralExpression(newValue, type, position);
			}
		}
		else
		{
			switch (operator)
			{
				case PLUS:
					if (isLiteralWithValue(leftOperand, 0))
						return rightOperand;
					if (isLiteralWithValue(rightOperand, 0))
						return leftOperand;
					if (rightOperand instanceof UnaryExpression)
						if (((UnaryExpression) rightOperand).operator == Symbol.Id.MINUS)
							return new BinaryExpression(leftOperand, Symbol.Id.MINUS,
									((UnaryExpression) rightOperand).operand);
					break;
				case MINUS:
					if (isLiteralWithValue(leftOperand, 0))
						return new UnaryExpression(Symbol.Id.MINUS, rightOperand, position);
					if (isLiteralWithValue(rightOperand, 0))
						return new UnaryExpression(Symbol.Id.MINUS, leftOperand, position);
					if (rightOperand instanceof UnaryExpression)
						if (((UnaryExpression) rightOperand).operator == Symbol.Id.MINUS)
							return new BinaryExpression(leftOperand, Symbol.Id.PLUS,
									((UnaryExpression) rightOperand).operand);
					break;
				case TIMES:
					if (isLiteralWithValue(leftOperand, 0) || isLiteralWithValue(rightOperand, 0))
						return new LiteralExpression(0, type, position);
					if (isLiteralWithValue(leftOperand, 1))
						return rightOperand;
					if (isLiteralWithValue(rightOperand, 1))
						return leftOperand;
					if (leftOperand instanceof LiteralExpression && rightOperand instanceof UnaryExpression)
						if (((UnaryExpression) rightOperand).operator == Symbol.Id.MINUS)
						{
							((LiteralExpression) leftOperand).value *= -1;
							return new BinaryExpression(leftOperand, operator, ((UnaryExpression) rightOperand).operand);
						}
					if (rightOperand instanceof LiteralExpression && leftOperand instanceof UnaryExpression)
						if (((UnaryExpression) leftOperand).operator == Symbol.Id.MINUS)
						{
							((LiteralExpression) rightOperand).value *= -1;
							return new BinaryExpression(rightOperand, operator, ((UnaryExpression) leftOperand).operand);
						}
					if (leftOperand instanceof UnaryExpression && rightOperand instanceof UnaryExpression)
						return new BinaryExpression(((UnaryExpression) leftOperand).operand, operator,
								((UnaryExpression) rightOperand).operand);
					if (leftOperand instanceof UnaryExpression)
						return new UnaryExpression(Symbol.Id.MINUS, new BinaryExpression(
								((UnaryExpression) leftOperand).operand, operator, rightOperand), position);
					if (rightOperand instanceof UnaryExpression)
						return new UnaryExpression(Symbol.Id.MINUS, new BinaryExpression(leftOperand, operator,
								((UnaryExpression) rightOperand).operand), position);
					break;
				case DIV:
					if (isLiteralWithValue(leftOperand, 0))
						return new LiteralExpression(0, type, position);
					if (isLiteralWithValue(rightOperand, 1))
						return leftOperand;
					if (leftOperand instanceof LiteralExpression && rightOperand instanceof UnaryExpression)
						if (((UnaryExpression) rightOperand).operator == Symbol.Id.MINUS)
						{
							((LiteralExpression) leftOperand).value *= -1;
							return new BinaryExpression(leftOperand, operator, ((UnaryExpression) rightOperand).operand);
						}
					if (rightOperand instanceof LiteralExpression && leftOperand instanceof UnaryExpression)
						if (((UnaryExpression) leftOperand).operator == Symbol.Id.MINUS)
						{
							((LiteralExpression) rightOperand).value *= -1;
							return new BinaryExpression(((UnaryExpression) leftOperand).operand, operator, rightOperand);
						}
					if (leftOperand instanceof UnaryExpression && rightOperand instanceof UnaryExpression)
						return new BinaryExpression(((UnaryExpression) leftOperand).operand, operator,
								((UnaryExpression) rightOperand).operand);
					if (leftOperand instanceof UnaryExpression)
						return new UnaryExpression(Symbol.Id.MINUS, new BinaryExpression(
								((UnaryExpression) leftOperand).operand, operator, rightOperand), position);
					if (rightOperand instanceof UnaryExpression)
						return new UnaryExpression(Symbol.Id.MINUS, new BinaryExpression(leftOperand, operator,
								((UnaryExpression) rightOperand).operand), position);
					break;
				case AND:
				case ANDTHEN:
					if (isLiteralWithValue(leftOperand, 0) || isLiteralWithValue(rightOperand, 0))
						return new LiteralExpression(0, type, position);
					if (isLiteralWithValue(leftOperand, 1))
						return rightOperand;
					if (isLiteralWithValue(rightOperand, 1))
						return leftOperand;
					break;
				case OR:
				case ORELSE:
					if (isLiteralWithValue(leftOperand, 1) || isLiteralWithValue(rightOperand, 1))
						return new LiteralExpression(1, type, position);
					if (isLiteralWithValue(leftOperand, 0))
						return rightOperand;
					if (isLiteralWithValue(rightOperand, 0))
						return leftOperand;
					break;
			}
		}
		return this;
	}
	
	private boolean isLiteralWithValue(Expression e, int v)
	{
		if (e instanceof LiteralExpression)
			return ((LiteralExpression) e).value == v;
		return false;
	}
	
	/**
	 * Die Methode führt die Kontextanalyse für diesen Ausdruck durch.
	 * 
	 * @param declarations
	 *            Die an dieser Stelle gültigen Deklarationen.
	 * @return Dieser Ausdruck.
	 * @throws CompileException
	 *             Während der Kontextanylyse wurde ein Fehler gefunden.
	 */
	Expression contextAnalysis(Declarations declarations) throws CompileException
	{
		leftOperand = leftOperand.contextAnalysis(declarations);
		rightOperand = rightOperand.contextAnalysis(declarations);
		switch (operator)
		{
			case PLUS:
			case MINUS:
			case TIMES:
			case DIV:
			case MOD:
				leftOperand = leftOperand.unBox();
				rightOperand = rightOperand.unBox();
				leftOperand.type.check(ClassDeclaration.intType, leftOperand.position);
				rightOperand.type.check(ClassDeclaration.intType, rightOperand.position);
				type = ClassDeclaration.intType;
				break;
			case GT:
			case GTEQ:
			case LT:
			case LTEQ:
				leftOperand = leftOperand.unBox();
				rightOperand = rightOperand.unBox();
				leftOperand.type.check(ClassDeclaration.intType, leftOperand.position);
				rightOperand.type.check(ClassDeclaration.intType, rightOperand.position);
				type = ClassDeclaration.boolType;
				break;
			case EQ:
			case NEQ:
				// Wenn einer der beiden Operanden NULL ist, muss der andere
				// ein Objekt sein (oder auch NULL)
				if (leftOperand.type == ClassDeclaration.nullType)
				{
					rightOperand = rightOperand.box(declarations);
				}
				else
					if (rightOperand.type == ClassDeclaration.nullType)
					{
						leftOperand = leftOperand.box(declarations);
					}
					else
					{
						// ansonsten wird versucht, die beiden Operanden in
						// Basisdatentypen zu wandeln
						leftOperand = leftOperand.unBox();
						rightOperand = rightOperand.unBox();
					}
				
				// Nun muss der Typ mindestens eines Operanden gleich oder eine
				// Ableitung des Typs des anderen Operanden sein.
				if (!leftOperand.type.isA(rightOperand.type) && !rightOperand.type.isA(leftOperand.type))
				{
					ClassDeclaration.typeError(leftOperand.type, rightOperand.position);
				}
				type = ClassDeclaration.boolType;
				break;
			case AND:
			case ANDTHEN:
			case OR:
			case ORELSE:
				leftOperand = leftOperand.unBox();
				rightOperand = rightOperand.unBox();
				leftOperand.type.check(ClassDeclaration.boolType, leftOperand.position);
				rightOperand.type.check(ClassDeclaration.boolType, rightOperand.position);
				type = ClassDeclaration.boolType;
				break;
			default:
				assert false;
		}
		return this;
	}
	
	/**
	 * Die Methode gibt diesen Ausdruck in einer Baumstruktur aus. Wenn der Typ des Ausdrucks bereits ermittelt wurde,
	 * wird er auch ausgegeben.
	 * 
	 * @param tree
	 *            Der Strom, in den die Ausgabe erfolgt.
	 */
	void print(TreeStream tree)
	{
		tree.println(operator + (type == null ? "" : " : " + type.identifier.name));
		tree.indent();
		leftOperand.print(tree);
		rightOperand.print(tree);
		tree.unindent();
	}
	
	/**
	 * Die Methode generiert den Assembler-Code für diesen Ausdruck. Sie geht davon aus, dass die Kontextanalyse vorher
	 * erfolgreich abgeschlossen wurde.
	 * 
	 * @param code
	 *            Der Strom, in den die Ausgabe erfolgt.
	 */
	void generateCode(CodeStream code)
	{
		generateCode(code, null);
	}
	
	/**
	 * Die Methode generiert den Assembler-Code für diesen Ausdruck. Sie geht davon aus, dass die Kontextanalyse vorher
	 * erfolgreich abgeschlossen wurde.
	 * 
	 * @param code
	 *            Der Strom, in den die Ausgabe erfolgt.
	 * @param endLabel
	 *            Label, mit der hinter die BinaryExpression gesprungen werden kann.
	 */
	void generateCode(CodeStream code, String endLabel)
	{
		String name = (endLabel == null) ? code.nextLabel() : endLabel;
		if (leftOperand instanceof BinaryExpression)
			((BinaryExpression) leftOperand).generateCode(code, name);
		else
			leftOperand.generateCode(code);
		if (operator == Symbol.Id.ANDTHEN || operator == Symbol.Id.ORELSE)
		{
			code.println("MRM R5, (R2)");
			if (operator == Symbol.Id.ANDTHEN)
			{
				code.println("MRI R6, 1");
				code.println("SUB R6, R5");
				code.println("MRR R5, R6");
			}
			code.println("JPC R5, " + name);
		}
		
		if (rightOperand instanceof BinaryExpression)
			((BinaryExpression) rightOperand).generateCode(code, name);
		else
			rightOperand.generateCode(code);
		code.println("; " + operator);
		code.println("MRM R5, (R2)");
		code.println("SUB R2, R1");
		code.println("MRM R6, (R2)");
		switch (operator)
		{
			case PLUS:
				code.println("ADD R6, R5");
				break;
			case MINUS:
				code.println("SUB R6, R5");
				break;
			case TIMES:
				code.println("MUL R6, R5");
				break;
			case DIV:
				String divLabel = code.nextLabel();
				code.println("JPC R5, " + divLabel);
				ExceptionClassDeclaration.divisionByZeroExceptionCall.generateCode(code);
				code.println("MRI R0, _end");
				code.println(divLabel + ":");
				code.println("DIV R6, R5");
				break;
			case MOD:
				code.println("MOD R6, R5");
				break;
			case GT:
				code.println("SUB R6, R5");
				code.println("ISP R6, R6");
				break;
			case GTEQ:
				code.println("SUB R6, R5");
				code.println("ISN R6, R6");
				code.println("XOR R6, R1");
				break;
			case LT:
				code.println("SUB R6, R5");
				code.println("ISN R6, R6");
				break;
			case LTEQ:
				code.println("SUB R6, R5");
				code.println("ISP R6, R6");
				code.println("XOR R6, R1");
				break;
			case EQ:
				code.println("SUB R6, R5");
				code.println("ISZ R6, R6");
				break;
			case NEQ:
				code.println("SUB R6, R5");
				code.println("ISZ R6, R6");
				code.println("XOR R6, R1");
				break;
			case AND:
			case ANDTHEN:
				code.println("AND R6, R5");
				break;
			case OR:
			case ORELSE:
				code.println("OR R6, R5");
				break;
			default:
				assert false;
		}
		code.println("MMR (R2), R6");
		if (endLabel == null)
			code.println(name + ":");
	}
	
	/**
	 * Die Methode gibt diesen Ausdruck in Textform aus.
	 */
	@Override
	public String toString()
	{
		return leftOperand + " " + operator + " " + rightOperand;
	}
}
